Skip to content

feat: implement should_apply_proposer_boost for gloas - #9233

Open
ensi321 wants to merge 13 commits into
unstablefrom
nc/should-apply-proposer-boost
Open

feat: implement should_apply_proposer_boost for gloas#9233
ensi321 wants to merge 13 commits into
unstablefrom
nc/should-apply-proposer-boost

Conversation

@ensi321

@ensi321 ensi321 commented Apr 18, 2026

Copy link
Copy Markdown
Member

Summary

Implements the should_apply_proposer_boost logic ethereum/consensus-specs/pull/4807 (gloas/fork-choice.md#new-should_apply_proposer_boost).

Was blocked on is_head_weak(); now rebased on top of #9654 (merged) and reuses its isHeadWeak() — boost-excluded attestation score + equivocator balance add-back — for the weak-parent check, exactly matching the spec's is_head_weak(store, parent_root) call.

Changes

  • Add ptcTimeliness and proposerIndex fields to ProtoBlock
  • Add isBlockPtcTimely to track PTC deadline timeliness
  • Add shouldApplyProposerBoost which withholds boost when the parent is a weak, equivocating block from the previous slot
  • Reuse isHeadWeak() from feat: implement forkchoice is_head_weak() and is_parent_strong() #9654 for the weak-parent check
  • Add findEquivocatingBlocks in ProtoArray to detect proposer equivocations
  • Gate proposer boost in getWeight on shouldApplyProposerBoost()
  • Pre-gloas blocks retain unconditional boost (backward compatible)

Note on test coverage

The existing gloas fork_choice vectors pass with or without this gate (the withhold branch executes but never decides the asserted head), so CI alone doesn't discriminate it. Discriminating vectors are proposed upstream in consensus-specs (test_should_apply_proposer_boost.py, MC/DC over the three apply conditions); the withheld case fails on unstable and passes on this branch.

AI Assistance Disclosure

Created with the help of Claude.

🤖 Generated with Claude Code

Implement the `should_apply_proposer_boost` logic from consensus-specs
commit 71d1151 (PR #4807). This addresses the builder reveal safety
concern where a colluding next-slot proposer could use proposer boost
to override a legitimately revealed block.

Changes:
- Add `ptcTimeliness` and `proposerIndex` fields to ProtoBlock
- Add `isBlockPtcTimely` to track PTC deadline timeliness
- Add `shouldApplyProposerBoost` which withholds boost when the parent
  is a weak, equivocating block from the previous slot
- Add `findEquivocatingBlocks` in ProtoArray to detect proposer
  equivocations by scanning for PTC-timely blocks at the same slot
  from the same proposer
- Gate proposer boost in `getWeight` on `shouldApplyProposerBoost()`
- Pre-gloas blocks retain unconditional boost (backward compatible)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ensi321
ensi321 requested a review from a team as a code owner April 18, 2026 02:23

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the Gloas fork-choice logic for proposer boost and PTC (Payload Timeliness Committee) timeliness. It adds ptcTimeliness and proposerIndex to block metadata and introduces the shouldApplyProposerBoost logic, which considers parent block weight and proposer equivocations. Feedback focuses on preventing a crash during the fork transition by using dynamic payload status for parent nodes and optimizing the performance of the equivocation check to avoid O(N) map iterations during head updates.

Comment thread packages/fork-choice/src/forkChoice/forkChoice.ts Outdated
Comment thread packages/fork-choice/src/protoArray/protoArray.ts Outdated
@ensi321
ensi321 marked this pull request as draft April 18, 2026 02:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bc6eb6ce5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/fork-choice/src/forkChoice/forkChoice.ts Outdated
@github-actions

github-actions Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

⚠️ Performance Alert ⚠️

Possible performance regression was detected for some benchmarks.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold.

Benchmark suite Current: 774d3e6 Previous: 9572bcc Ratio
send data - 1000 4096B messages 54.297 ms/op 17.009 ms/op 3.19
Full benchmark results
Benchmark suite Current: 774d3e6 Previous: 9572bcc Ratio
getPubkeys - native cache - req 1000 vs - 250000 vc 418.77 us/op 410.03 us/op 1.02
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 40.655 us/op 39.409 us/op 1.03
BLS verify - blst 931.35 us/op 885.39 us/op 1.05
BLS verifyMultipleSignatures 3 - blst 1.4029 ms/op 1.3878 ms/op 1.01
BLS verifyMultipleSignatures 8 - blst 2.3683 ms/op 2.2394 ms/op 1.06
BLS verifyMultipleSignatures 32 - blst 7.8678 ms/op 7.3835 ms/op 1.07
BLS verifyMultipleSignatures 64 - blst 15.055 ms/op 14.236 ms/op 1.06
BLS verifyMultipleSignatures 128 - blst 28.303 ms/op 26.872 ms/op 1.05
BLS deserializing 10000 signatures 669.31 ms/op 632.30 ms/op 1.06
BLS deserializing 100000 signatures 6.6683 s/op 6.3270 s/op 1.05
BLS verifyMultipleSignatures - same message - 3 - blst 985.21 us/op 947.72 us/op 1.04
BLS verifyMultipleSignatures - same message - 8 - blst 1.1199 ms/op 1.0760 ms/op 1.04
BLS verifyMultipleSignatures - same message - 32 - blst 1.7631 ms/op 1.6931 ms/op 1.04
BLS verifyMultipleSignatures - same message - 64 - blst 2.6221 ms/op 2.5096 ms/op 1.04
BLS verifyMultipleSignatures - same message - 128 - blst 4.3473 ms/op 4.1323 ms/op 1.05
BLS aggregatePubkeys 32 - blst 20.037 us/op 19.404 us/op 1.03
BLS aggregatePubkeys 128 - blst 72.587 us/op 70.530 us/op 1.03
getSlashingsAndExits - default max 45.027 us/op 40.313 us/op 1.12
getSlashingsAndExits - 2k 344.96 us/op 334.86 us/op 1.03
proposeBlockBody type=full, size=empty 730.25 us/op 713.18 us/op 1.02
isKnown best case - 1 super set check 176.00 ns/op 185.00 ns/op 0.95
isKnown normal case - 2 super set checks 176.00 ns/op 167.00 ns/op 1.05
isKnown worse case - 16 super set checks 173.00 ns/op 166.00 ns/op 1.04
validate api signedAggregateAndProof - struct 1.5564 ms/op 1.8182 ms/op 0.86
validate gossip signedAggregateAndProof - struct 1.5529 ms/op 1.7689 ms/op 0.88
batch validate gossip attestation - vc 640000 - chunk 32 125.88 us/op 124.81 us/op 1.01
batch validate gossip attestation - vc 640000 - chunk 64 108.27 us/op 106.00 us/op 1.02
batch validate gossip attestation - vc 640000 - chunk 128 97.516 us/op 95.514 us/op 1.02
batch validate gossip attestation - vc 640000 - chunk 256 97.484 us/op 98.019 us/op 0.99
bytes32 toHexString 300.00 ns/op 299.00 ns/op 1.00
bytes32 Buffer.toString(hex) 190.00 ns/op 174.00 ns/op 1.09
bytes32 Buffer.toString(hex) from Uint8Array 261.00 ns/op 242.00 ns/op 1.08
bytes32 Buffer.toString(hex) + 0x 187.00 ns/op 172.00 ns/op 1.09
Return object 10000 times 0.21840 ns/op 0.21320 ns/op 1.02
Throw Error 10000 times 3.4719 us/op 3.3113 us/op 1.05
toHex 99.027 ns/op 96.811 ns/op 1.02
Buffer.from 91.944 ns/op 90.173 ns/op 1.02
shared Buffer 65.042 ns/op 63.259 ns/op 1.03
fastMsgIdFn sha256 / 200 bytes 1.5270 us/op 1.4890 us/op 1.03
fastMsgIdFn h32 xxhash / 200 bytes 165.00 ns/op 153.00 ns/op 1.08
fastMsgIdFn h64 xxhash / 200 bytes 213.00 ns/op 207.00 ns/op 1.03
fastMsgIdFn sha256 / 1000 bytes 4.8800 us/op 4.7340 us/op 1.03
fastMsgIdFn h32 xxhash / 1000 bytes 254.00 ns/op 246.00 ns/op 1.03
fastMsgIdFn h64 xxhash / 1000 bytes 262.00 ns/op 258.00 ns/op 1.02
fastMsgIdFn sha256 / 10000 bytes 42.826 us/op 41.784 us/op 1.02
fastMsgIdFn h32 xxhash / 10000 bytes 1.2930 us/op 1.2530 us/op 1.03
fastMsgIdFn h64 xxhash / 10000 bytes 829.00 ns/op 806.00 ns/op 1.03
send data - 1000 256B messages 4.0470 ms/op 3.9887 ms/op 1.01
send data - 1000 512B messages 5.3327 ms/op 4.7722 ms/op 1.12
send data - 1000 1024B messages 5.4966 ms/op 5.2009 ms/op 1.06
send data - 1000 1200B messages 6.3035 ms/op 6.3617 ms/op 0.99
send data - 1000 2048B messages 12.592 ms/op 9.7093 ms/op 1.30
send data - 1000 4096B messages 54.297 ms/op 17.009 ms/op 3.19
send data - 1000 16384B messages 385.12 ms/op 364.64 ms/op 1.06
send data - 1000 65536B messages 1.9733 s/op 1.9527 s/op 1.01
enrSubnets - fastDeserialize 64 bits 842.00 ns/op 745.00 ns/op 1.13
enrSubnets - ssz BitVector 64 bits 287.00 ns/op 268.00 ns/op 1.07
enrSubnets - fastDeserialize 4 bits 106.00 ns/op 101.00 ns/op 1.05
enrSubnets - ssz BitVector 4 bits 292.00 ns/op 266.00 ns/op 1.10
prioritizePeers score -10:0 att 32-0.1 sync 2-0 209.57 us/op 208.81 us/op 1.00
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 233.77 us/op 238.31 us/op 0.98
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 344.37 us/op 345.88 us/op 1.00
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 601.51 us/op 615.94 us/op 0.98
prioritizePeers score 0:0 att 64-1 sync 4-1 696.21 us/op 723.46 us/op 0.96
array of 16000 items push then shift 1.3346 us/op 1.3223 us/op 1.01
LinkedList of 16000 items push then shift 7.4770 ns/op 7.3170 ns/op 1.02
array of 16000 items push then pop 80.264 ns/op 80.156 ns/op 1.00
LinkedList of 16000 items push then pop 6.2260 ns/op 6.0370 ns/op 1.03
array of 24000 items push then shift 1.9754 us/op 1.9536 us/op 1.01
LinkedList of 24000 items push then shift 7.0450 ns/op 6.8200 ns/op 1.03
array of 24000 items push then pop 116.00 ns/op 113.69 ns/op 1.02
LinkedList of 24000 items push then pop 6.2360 ns/op 6.0930 ns/op 1.02
intersect bitArray bitLen 8 4.0380 ns/op 3.8990 ns/op 1.04
intersect array and set length 8 30.528 ns/op 29.693 ns/op 1.03
intersect bitArray bitLen 128 26.997 ns/op 23.538 ns/op 1.15
intersect array and set length 128 516.52 ns/op 502.58 ns/op 1.03
bitArray.getTrueBitIndexes() bitLen 128 976.00 ns/op 929.00 ns/op 1.05
bitArray.getTrueBitIndexes() bitLen 248 1.7570 us/op 1.6910 us/op 1.04
bitArray.getTrueBitIndexes() bitLen 512 3.6080 us/op 3.5240 us/op 1.02
Full columns - reconstruct all 6 blobs 136.80 us/op 189.90 us/op 0.72
Full columns - reconstruct half of the blobs out of 6 76.011 us/op 69.894 us/op 1.09
Full columns - reconstruct single blob out of 6 33.765 us/op 32.118 us/op 1.05
Half columns - reconstruct all 6 blobs 405.13 ms/op 402.68 ms/op 1.01
Half columns - reconstruct half of the blobs out of 6 204.17 ms/op 201.72 ms/op 1.01
Half columns - reconstruct single blob out of 6 72.802 ms/op 68.663 ms/op 1.06
Set add up to 64 items then delete first 2.3433 us/op 1.6793 us/op 1.40
OrderedSet add up to 64 items then delete first 3.7096 us/op 2.5328 us/op 1.46
Set add up to 64 items then delete last 2.4681 us/op 1.8918 us/op 1.30
OrderedSet add up to 64 items then delete last 3.5208 us/op 2.8024 us/op 1.26
Set add up to 64 items then delete middle 2.2388 us/op 1.8855 us/op 1.19
OrderedSet add up to 64 items then delete middle 5.0829 us/op 4.2658 us/op 1.19
Set add up to 128 items then delete first 4.6454 us/op 3.7985 us/op 1.22
OrderedSet add up to 128 items then delete first 7.1790 us/op 5.7452 us/op 1.25
Set add up to 128 items then delete last 4.0117 us/op 3.6360 us/op 1.10
OrderedSet add up to 128 items then delete last 6.0926 us/op 5.3943 us/op 1.13
Set add up to 128 items then delete middle 4.1440 us/op 3.6444 us/op 1.14
OrderedSet add up to 128 items then delete middle 12.347 us/op 11.437 us/op 1.08
Set add up to 256 items then delete first 8.2320 us/op 7.4896 us/op 1.10
OrderedSet add up to 256 items then delete first 12.725 us/op 11.748 us/op 1.08
Set add up to 256 items then delete last 7.9199 us/op 7.1732 us/op 1.10
OrderedSet add up to 256 items then delete last 12.173 us/op 11.131 us/op 1.09
Set add up to 256 items then delete middle 7.9751 us/op 7.2033 us/op 1.11
OrderedSet add up to 256 items then delete middle 38.077 us/op 34.694 us/op 1.10
runFastConfirmationRules vc:100000 bc:96 eq:0 5.2269 ms/op 4.5562 ms/op 1.15
runFastConfirmationRules vc:600000 bc:96 eq:0 37.399 ms/op 34.511 ms/op 1.08
runFastConfirmationRules vc:1000000 bc:96 eq:0 62.794 ms/op 69.593 ms/op 0.90
runFastConfirmationRules vc:600000 bc:320 eq:0 37.635 ms/op 34.832 ms/op 1.08
runFastConfirmationRules vc:100000 bc:96 eq:1000 1.2071 s/op 1.1532 s/op 1.05
pass gossip attestations to forkchoice per slot 2.7231 ms/op 2.5602 ms/op 1.06
forkChoice updateHead vc 100000 bc 64 eq 0 429.31 us/op 430.22 us/op 1.00
forkChoice updateHead vc 600000 bc 64 eq 0 2.5342 ms/op 2.5704 ms/op 0.99
forkChoice updateHead vc 1000000 bc 64 eq 0 4.2463 ms/op 4.2644 ms/op 1.00
forkChoice updateHead vc 600000 bc 320 eq 0 2.5871 ms/op 2.5814 ms/op 1.00
forkChoice updateHead vc 600000 bc 1200 eq 0 2.6336 ms/op 2.6134 ms/op 1.01
forkChoice updateHead vc 600000 bc 7200 eq 0 3.2260 ms/op 3.0593 ms/op 1.05
forkChoice updateHead vc 600000 bc 64 eq 1000 2.5676 ms/op 2.5751 ms/op 1.00
forkChoice updateHead vc 600000 bc 64 eq 10000 2.6478 ms/op 2.7033 ms/op 0.98
forkChoice updateHead vc 600000 bc 64 eq 300000 7.3620 ms/op 7.2562 ms/op 1.01
forkChoice updateHead vc 600000 bc 64 eq 0 gloas boosted 2.6486 ms/op
computeDeltas 1400000 validators 0% inactive 12.871 ms/op 12.565 ms/op 1.02
computeDeltas 1400000 validators 10% inactive 12.101 ms/op 11.868 ms/op 1.02
computeDeltas 1400000 validators 20% inactive 11.493 ms/op 11.545 ms/op 1.00
computeDeltas 1400000 validators 50% inactive 9.3906 ms/op 9.0707 ms/op 1.04
computeDeltas 2100000 validators 0% inactive 19.316 ms/op 18.851 ms/op 1.02
computeDeltas 2100000 validators 10% inactive 18.218 ms/op 18.447 ms/op 0.99
computeDeltas 2100000 validators 20% inactive 17.146 ms/op 17.283 ms/op 0.99
computeDeltas 2100000 validators 50% inactive 11.440 ms/op 11.624 ms/op 0.98
altair processAttestation - 250000 vs - 7PWei normalcase 1.9139 ms/op 1.6205 ms/op 1.18
altair processAttestation - 250000 vs - 7PWei worstcase 3.0613 ms/op 2.4577 ms/op 1.25
altair processAttestation - setStatus - 1/6 committees join 109.55 us/op 104.50 us/op 1.05
altair processAttestation - setStatus - 1/3 committees join 217.62 us/op 206.59 us/op 1.05
altair processAttestation - setStatus - 1/2 committees join 307.47 us/op 289.83 us/op 1.06
altair processAttestation - setStatus - 2/3 committees join 399.76 us/op 377.38 us/op 1.06
altair processAttestation - setStatus - 4/5 committees join 569.43 us/op 530.01 us/op 1.07
altair processAttestation - setStatus - 100% committees join 657.78 us/op 624.88 us/op 1.05
altair processBlock - 250000 vs - 7PWei normalcase 5.5754 ms/op 3.8603 ms/op 1.44
altair processBlock - 250000 vs - 7PWei normalcase hashState 24.682 ms/op 20.050 ms/op 1.23
altair processBlock - 250000 vs - 7PWei worstcase 28.527 ms/op 24.041 ms/op 1.19
altair processBlock - 250000 vs - 7PWei worstcase hashState 62.753 ms/op 52.685 ms/op 1.19
phase0 processBlock - 250000 vs - 7PWei normalcase 1.4452 ms/op 1.3069 ms/op 1.11
phase0 processBlock - 250000 vs - 7PWei worstcase 22.561 ms/op 19.802 ms/op 1.14
altair processEth1Data - 250000 vs - 7PWei normalcase 315.73 us/op 303.31 us/op 1.04
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.4200 us/op 3.2980 us/op 1.04
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 21.349 us/op 20.461 us/op 1.04
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 6.3220 us/op 5.8870 us/op 1.07
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.9670 us/op 3.7320 us/op 1.06
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 95.522 us/op 90.899 us/op 1.05
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.5502 ms/op 1.4692 ms/op 1.06
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 2.0192 ms/op 1.9070 ms/op 1.06
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 2.0031 ms/op 1.9005 ms/op 1.05
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 4.1751 ms/op 3.7829 ms/op 1.10
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.2902 ms/op 2.1368 ms/op 1.07
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.6214 ms/op 4.1948 ms/op 1.10
Tree 40 250000 create 383.91 ms/op 320.72 ms/op 1.20
Tree 40 250000 get(125000) 99.589 ns/op 102.96 ns/op 0.97
Tree 40 250000 set(125000) 1.1045 us/op 1.0540 us/op 1.05
Tree 40 250000 toArray() 19.178 ms/op 10.984 ms/op 1.75
Tree 40 250000 iterate all - toArray() + loop 20.587 ms/op 11.959 ms/op 1.72
Tree 40 250000 iterate all - get(i) 43.578 ms/op 37.541 ms/op 1.16
Array 250000 create 2.8140 ms/op 2.5500 ms/op 1.10
Array 250000 clone - spread 855.21 us/op 791.67 us/op 1.08
Array 250000 get(125000) 0.31800 ns/op 0.29500 ns/op 1.08
Array 250000 set(125000) 0.31900 ns/op 0.29700 ns/op 1.07
Array 250000 iterate all - loop 61.137 us/op 57.571 us/op 1.06
phase0 afterProcessEpoch - 250000 vs - 7PWei 44.084 ms/op 66.811 ms/op 0.66
Array.fill - length 1000000 4.4018 ms/op 4.0800 ms/op 1.08
Array push - length 1000000 21.292 ms/op 15.093 ms/op 1.41
Array.get 0.22172 ns/op 0.20557 ns/op 1.08
Uint8Array.get 0.24805 ns/op 0.27468 ns/op 0.90
phase0 beforeProcessEpoch - 250000 vs - 7PWei 21.982 ms/op 17.236 ms/op 1.28
altair processEpoch - mainnet_e81889 258.67 ms/op 206.98 ms/op 1.25
mainnet_e81889 - altair beforeProcessEpoch 39.784 ms/op 37.438 ms/op 1.06
mainnet_e81889 - altair processJustificationAndFinalization 6.0050 us/op 4.5790 us/op 1.31
mainnet_e81889 - altair processInactivityUpdates 3.8245 ms/op 3.6330 ms/op 1.05
mainnet_e81889 - altair processRewardsAndPenalties 20.386 ms/op 17.964 ms/op 1.13
mainnet_e81889 - altair processRegistryUpdates 628.00 ns/op 566.00 ns/op 1.11
mainnet_e81889 - altair processSlashings 169.00 ns/op 137.00 ns/op 1.23
mainnet_e81889 - altair processEth1DataReset 163.00 ns/op 134.00 ns/op 1.22
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.7740 ms/op 1.7140 ms/op 1.04
mainnet_e81889 - altair processSlashingsReset 771.00 ns/op 690.00 ns/op 1.12
mainnet_e81889 - altair processRandaoMixesReset 1.0390 us/op 929.00 ns/op 1.12
mainnet_e81889 - altair processHistoricalRootsUpdate 171.00 ns/op 137.00 ns/op 1.25
mainnet_e81889 - altair processParticipationFlagUpdates 497.00 ns/op 444.00 ns/op 1.12
mainnet_e81889 - altair processSyncCommitteeUpdates 138.00 ns/op 108.00 ns/op 1.28
mainnet_e81889 - altair afterProcessEpoch 44.951 ms/op 40.876 ms/op 1.10
capella processEpoch - mainnet_e217614 938.77 ms/op 669.31 ms/op 1.40
mainnet_e217614 - capella beforeProcessEpoch 106.50 ms/op 57.308 ms/op 1.86
mainnet_e217614 - capella processJustificationAndFinalization 5.6710 us/op 4.8780 us/op 1.16
mainnet_e217614 - capella processInactivityUpdates 19.419 ms/op 13.908 ms/op 1.40
mainnet_e217614 - capella processRewardsAndPenalties 99.097 ms/op 99.616 ms/op 0.99
mainnet_e217614 - capella processRegistryUpdates 4.8210 us/op 4.6400 us/op 1.04
mainnet_e217614 - capella processSlashings 170.00 ns/op 138.00 ns/op 1.23
mainnet_e217614 - capella processEth1DataReset 334.00 ns/op 135.00 ns/op 2.47
mainnet_e217614 - capella processEffectiveBalanceUpdates 32.466 ms/op 10.548 ms/op 3.08
mainnet_e217614 - capella processSlashingsReset 1.0850 us/op 717.00 ns/op 1.51
mainnet_e217614 - capella processRandaoMixesReset 2.1900 us/op 1000.0 ns/op 2.19
mainnet_e217614 - capella processHistoricalRootsUpdate 334.00 ns/op 137.00 ns/op 2.44
mainnet_e217614 - capella processParticipationFlagUpdates 1.4250 us/op 455.00 ns/op 3.13
mainnet_e217614 - capella afterProcessEpoch 117.97 ms/op 107.66 ms/op 1.10
phase0 processEpoch - mainnet_e58758 235.38 ms/op 207.25 ms/op 1.14
mainnet_e58758 - phase0 beforeProcessEpoch 57.271 ms/op 47.651 ms/op 1.20
mainnet_e58758 - phase0 processJustificationAndFinalization 5.4950 us/op 4.6400 us/op 1.18
mainnet_e58758 - phase0 processRewardsAndPenalties 18.374 ms/op 16.880 ms/op 1.09
mainnet_e58758 - phase0 processRegistryUpdates 2.4690 us/op 2.3230 us/op 1.06
mainnet_e58758 - phase0 processSlashings 178.00 ns/op 136.00 ns/op 1.31
mainnet_e58758 - phase0 processEth1DataReset 171.00 ns/op 289.00 ns/op 0.59
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 879.19 us/op 973.50 us/op 0.90
mainnet_e58758 - phase0 processSlashingsReset 1.0250 us/op 923.00 ns/op 1.11
mainnet_e58758 - phase0 processRandaoMixesReset 1.1440 us/op 1.0050 us/op 1.14
mainnet_e58758 - phase0 processHistoricalRootsUpdate 173.00 ns/op 135.00 ns/op 1.28
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.0570 us/op 929.00 ns/op 1.14
mainnet_e58758 - phase0 afterProcessEpoch 36.423 ms/op 34.726 ms/op 1.05
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.4639 ms/op 1.0162 ms/op 1.44
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.8640 ms/op 1.2351 ms/op 1.51
altair processInactivityUpdates - 250000 normalcase 12.873 ms/op 10.976 ms/op 1.17
altair processInactivityUpdates - 250000 worstcase 12.858 ms/op 11.094 ms/op 1.16
phase0 processRegistryUpdates - 250000 normalcase 3.0970 us/op 2.3920 us/op 1.29
phase0 processRegistryUpdates - 250000 badcase_full_deposits 187.63 us/op 142.93 us/op 1.31
phase0 processRegistryUpdates - 250000 worstcase 0.5 82.093 ms/op 67.018 ms/op 1.22
altair processRewardsAndPenalties - 250000 normalcase 19.744 ms/op 14.935 ms/op 1.32
altair processRewardsAndPenalties - 250000 worstcase 22.039 ms/op 13.911 ms/op 1.58
phase0 getAttestationDeltas - 250000 normalcase 6.2185 ms/op 5.7096 ms/op 1.09
phase0 getAttestationDeltas - 250000 worstcase 6.2140 ms/op 5.7452 ms/op 1.08
phase0 processSlashings - 250000 worstcase 70.676 us/op 61.016 us/op 1.16
altair processSyncCommitteeUpdates - 250000 15.859 ms/op 10.012 ms/op 1.58
BeaconState.hashTreeRoot - No change 309.00 ns/op 180.00 ns/op 1.72
BeaconState.hashTreeRoot - 1 full validator 85.528 us/op 72.204 us/op 1.18
BeaconState.hashTreeRoot - 32 full validator 983.53 us/op 793.62 us/op 1.24
BeaconState.hashTreeRoot - 512 full validator 9.9025 ms/op 7.2324 ms/op 1.37
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 122.09 us/op 89.418 us/op 1.37
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.7503 ms/op 1.1457 ms/op 1.53
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 19.846 ms/op 14.560 ms/op 1.36
BeaconState.hashTreeRoot - 1 balances 91.082 us/op 61.324 us/op 1.49
BeaconState.hashTreeRoot - 32 balances 892.73 us/op 584.43 us/op 1.53
BeaconState.hashTreeRoot - 512 balances 7.3834 ms/op 4.6723 ms/op 1.58
BeaconState.hashTreeRoot - 250000 balances 147.16 ms/op 97.083 ms/op 1.52
aggregationBits - 2048 els - zipIndexesInBitList 22.929 us/op 18.990 us/op 1.21
regular array get 100000 times 24.519 us/op 22.660 us/op 1.08
wrappedArray get 100000 times 24.461 us/op 22.698 us/op 1.08
arrayWithProxy get 100000 times 10.001 ms/op 10.226 ms/op 0.98
ssz.Root.equals 22.796 ns/op 21.406 ns/op 1.06
byteArrayEquals 22.596 ns/op 21.197 ns/op 1.07
Buffer.compare 9.3880 ns/op 8.9070 ns/op 1.05
processSlot - 1 slots 10.962 us/op 8.3220 us/op 1.32
processSlot - 32 slots 2.4415 ms/op 1.6138 ms/op 1.51
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 5.6371 ms/op 1.8864 ms/op 2.99
getCommitteeAssignments - req 1 vs - 250000 vc 1.7642 ms/op 1.6663 ms/op 1.06
getCommitteeAssignments - req 100 vs - 250000 vc 3.6217 ms/op 3.4240 ms/op 1.06
getCommitteeAssignments - req 1000 vs - 250000 vc 3.8965 ms/op 3.6992 ms/op 1.05
findModifiedValidators - 10000 modified validators 1.0632 s/op 807.52 ms/op 1.32
findModifiedValidators - 1000 modified validators 794.64 ms/op 537.24 ms/op 1.48
findModifiedValidators - 100 modified validators 422.81 ms/op 365.89 ms/op 1.16
findModifiedValidators - 10 modified validators 345.88 ms/op 308.92 ms/op 1.12
findModifiedValidators - 1 modified validators 314.29 ms/op 269.42 ms/op 1.17
findModifiedValidators - no difference 235.46 ms/op 232.37 ms/op 1.01
migrate state 1500000 validators, 3400 modified, 2000 new 3.8355 s/op 2.9430 s/op 1.30
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.8800 ns/op 3.6500 ns/op 1.06
state getBlockRootAtSlot - 250000 vs - 7PWei 432.50 ns/op 297.06 ns/op 1.46
computeProposerIndex 100000 validators 1.4397 ms/op 1.3635 ms/op 1.06
getNextSyncCommitteeIndices 1000 validators 3.0522 ms/op 2.8753 ms/op 1.06
getNextSyncCommitteeIndices 10000 validators 26.713 ms/op 25.899 ms/op 1.03
getNextSyncCommitteeIndices 100000 validators 94.949 ms/op 87.450 ms/op 1.09
computeProposers - vc 250000 573.50 us/op 543.06 us/op 1.06
computeEpochShuffling - vc 250000 42.167 ms/op 38.432 ms/op 1.10
getNextSyncCommittee - vc 250000 10.115 ms/op 9.3943 ms/op 1.08
nodejs block root to RootHex using toHex 103.33 ns/op 99.744 ns/op 1.04
nodejs block root to RootHex using toRootHex 65.139 ns/op 66.086 ns/op 0.99
nodejs fromHex(blob) 916.54 us/op 1.0321 ms/op 0.89
nodejs fromHexInto(blob) 662.60 us/op 647.82 us/op 1.02
nodejs block root to RootHex using the deprecated toHexString 626.36 ns/op 606.67 ns/op 1.03
nodejs byteArrayEquals 32 bytes (block root) 27.378 ns/op 26.567 ns/op 1.03
nodejs byteArrayEquals 48 bytes (pubkey) 39.566 ns/op 38.179 ns/op 1.04
nodejs byteArrayEquals 96 bytes (signature) 36.577 ns/op 37.214 ns/op 0.98
nodejs byteArrayEquals 1024 bytes 44.872 ns/op 45.135 ns/op 0.99
nodejs byteArrayEquals 131072 bytes (blob) 1.8717 us/op 1.8201 us/op 1.03
browser block root to RootHex using toHex 151.88 ns/op 147.80 ns/op 1.03
browser block root to RootHex using toRootHex 137.59 ns/op 130.45 ns/op 1.05
browser fromHex(blob) 1.9337 ms/op 1.7267 ms/op 1.12
browser fromHexInto(blob) 659.07 us/op 659.58 us/op 1.00
browser block root to RootHex using the deprecated toHexString 448.36 ns/op 387.08 ns/op 1.16
browser byteArrayEquals 32 bytes (block root) 29.566 ns/op 28.016 ns/op 1.06
browser byteArrayEquals 48 bytes (pubkey) 41.696 ns/op 39.513 ns/op 1.06
browser byteArrayEquals 96 bytes (signature) 78.512 ns/op 74.013 ns/op 1.06
browser byteArrayEquals 1024 bytes 797.14 ns/op 749.04 ns/op 1.06
browser byteArrayEquals 131072 bytes (blob) 99.863 us/op 94.366 us/op 1.06

by benchmarkbot/action

GrapeBaBa added a commit to GrapeBaBa/lodestar that referenced this pull request May 1, 2026
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into
the existing `forkChoiceTest` runner. The on-disk layout matches the
standard spec-test layout
(`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`),
so it slots in alongside `fork_choice` and `sync` runners.

Three test-only accommodations the compliance fixtures require:

1. `bls_setting: 2` — every compliance fixture uses placeholder
   signatures. Pass `validSignatures: testcase.meta?.bls_setting !==
   BigInt(1)` to `chain.processBlock` so verification short-circuits.
   Standard `fork_choice` fixtures use `bls_setting: 1` so behavior
   there is unchanged.

2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally
   re-import the same block (`dup_shift` mutations in their
   `meta.yaml`). Spec semantics for `on_block(store, known_block)` is
   a no-op success. Production block import correctly rejects with
   ALREADY_KNOWN; this runner treats that case as success only when
   the step is `valid: true`.

3. Cross-epoch attestation shuffling — `on_attestation` decodes
   aggregation_bits using the state at the attestation's target
   checkpoint, not the head state. The runner now resolves the right
   shuffling via ShufflingCache + regen (mirroring the production
   validation path) instead of `headState.epochCtx.getIndexedAttestation`,
   which only worked when the attestation's epoch happened to be in
   the head's epoch cache (±1 epoch) and broke on cross-epoch fork
   attestations surfaced by the compliance suite.

Adds support for two compliance-only check fields:

- `viable_for_head_roots_and_weights` (consensus-specs#3831): compared
  via `getViableHeads()`. Both sides are sorted by root before
  comparison since the spec doesn't fix order.
- `head_payload_status` (gloas): mapped between our internal enum
  ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0,
  FULL=1, PENDING=2).

Pass rate against the latest comptests workflow `small.tar.gz` artifact:

  fulu/fork_choice_compliance: 253/1472 cases pass (17.2%)

Top remaining failures:
- ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced
  a `block.proposer_index == get_beacon_proposer_index(head_state)`
  guard in `update_proposer_boost_root` that we do not yet implement;
  affects all forks (not just gloas equivocation handling). Tracked
  for follow-up alongside ChainSafe#9233.
- ~1% `Invalid viable heads` — proposer-boost rounding on minimal
  preset (see `getViableHeads()` weight note).
GrapeBaBa added a commit to GrapeBaBa/lodestar that referenced this pull request May 1, 2026
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into
the existing `forkChoiceTest` runner. The on-disk layout matches the
standard spec-test layout
(`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`),
so it slots in alongside `fork_choice` and `sync` runners.

Three test-only accommodations the compliance fixtures require:

1. `bls_setting: 2` — every compliance fixture uses placeholder
   signatures. Pass `validSignatures: testcase.meta?.bls_setting !==
   BigInt(1)` to `chain.processBlock` so verification short-circuits.
   Standard `fork_choice` fixtures use `bls_setting: 1` so behavior
   there is unchanged.

2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally
   re-import the same block (`dup_shift` mutations in their
   `meta.yaml`). Spec semantics for `on_block(store, known_block)` is
   a no-op success. Production block import correctly rejects with
   ALREADY_KNOWN; this runner treats that case as success only when
   the step is `valid: true`.

3. Cross-epoch attestation shuffling — `on_attestation` decodes
   aggregation_bits using the state at the attestation's target
   checkpoint, not the head state. The runner now resolves the right
   shuffling via ShufflingCache + regen (mirroring the production
   validation path) instead of `headState.epochCtx.getIndexedAttestation`,
   which only worked when the attestation's epoch happened to be in
   the head's epoch cache (±1 epoch) and broke on cross-epoch fork
   attestations surfaced by the compliance suite.

Adds support for two compliance-only check fields:

- `viable_for_head_roots_and_weights` (consensus-specs#3831): compared
  via `getViableHeads()`. Both sides are sorted by root before
  comparison since the spec doesn't fix order.
- `head_payload_status` (gloas): mapped between our internal enum
  ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0,
  FULL=1, PENDING=2).

Pass rate against the latest comptests workflow `small.tar.gz` artifact:

  fulu/fork_choice_compliance: 253/1472 cases pass (17.2%)

Top remaining failures:
- ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced
  a `block.proposer_index == get_beacon_proposer_index(head_state)`
  guard in `update_proposer_boost_root` that we do not yet implement;
  affects all forks (not just gloas equivocation handling). Tracked
  for follow-up alongside ChainSafe#9233.
- ~1% `Invalid viable heads` — proposer-boost rounding on minimal
  preset (see `getViableHeads()` weight note).
GrapeBaBa added a commit to GrapeBaBa/lodestar that referenced this pull request May 1, 2026
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into
the existing `forkChoiceTest` runner. The on-disk layout matches the
standard spec-test layout
(`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`),
so it slots in alongside `fork_choice` and `sync` runners.

Three test-only accommodations the compliance fixtures require:

1. `bls_setting: 2` — every compliance fixture uses placeholder
   signatures. Pass `validSignatures: testcase.meta?.bls_setting !==
   BigInt(1)` to `chain.processBlock` so verification short-circuits.
   Standard `fork_choice` fixtures use `bls_setting: 1` so behavior
   there is unchanged.

2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally
   re-import the same block (`dup_shift` mutations in their
   `meta.yaml`). Spec semantics for `on_block(store, known_block)` is
   a no-op success. Production block import correctly rejects with
   ALREADY_KNOWN; this runner treats that case as success only when
   the step is `valid: true`.

3. Cross-epoch attestation shuffling — `on_attestation` decodes
   aggregation_bits using the state at the attestation's target
   checkpoint, not the head state. The runner now resolves the right
   shuffling via ShufflingCache + regen (mirroring the production
   validation path) instead of `headState.epochCtx.getIndexedAttestation`,
   which only worked when the attestation's epoch happened to be in
   the head's epoch cache (±1 epoch) and broke on cross-epoch fork
   attestations surfaced by the compliance suite.

Adds support for two compliance-only check fields:

- `viable_for_head_roots_and_weights` (consensus-specs#3831): compared
  via `getViableHeads()`. Both sides are sorted by root before
  comparison since the spec doesn't fix order.
- `head_payload_status` (gloas): mapped between our internal enum
  ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0,
  FULL=1, PENDING=2).

Pass rate against the latest comptests workflow `small.tar.gz` artifact:

  fulu/fork_choice_compliance: 253/1472 cases pass (17.2%)

Top remaining failures:
- ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced
  a `block.proposer_index == get_beacon_proposer_index(head_state)`
  guard in `update_proposer_boost_root` that we do not yet implement;
  affects all forks (not just gloas equivocation handling). Tracked
  for follow-up alongside ChainSafe#9233.
- ~1% `Invalid viable heads` — proposer-boost rounding on minimal
  preset (see `getViableHeads()` weight note).
GrapeBaBa added a commit to GrapeBaBa/lodestar that referenced this pull request Jul 7, 2026
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into
the existing `forkChoiceTest` runner. The on-disk layout matches the
standard spec-test layout
(`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`),
so it slots in alongside `fork_choice` and `sync` runners.

Three test-only accommodations the compliance fixtures require:

1. `bls_setting: 2` — every compliance fixture uses placeholder
   signatures. Pass `validSignatures: testcase.meta?.bls_setting !==
   BigInt(1)` to `chain.processBlock` so verification short-circuits.
   Standard `fork_choice` fixtures use `bls_setting: 1` so behavior
   there is unchanged.

2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally
   re-import the same block (`dup_shift` mutations in their
   `meta.yaml`). Spec semantics for `on_block(store, known_block)` is
   a no-op success. Production block import correctly rejects with
   ALREADY_KNOWN; this runner treats that case as success only when
   the step is `valid: true`.

3. Cross-epoch attestation shuffling — `on_attestation` decodes
   aggregation_bits using the state at the attestation's target
   checkpoint, not the head state. The runner now resolves the right
   shuffling via ShufflingCache + regen (mirroring the production
   validation path) instead of `headState.epochCtx.getIndexedAttestation`,
   which only worked when the attestation's epoch happened to be in
   the head's epoch cache (±1 epoch) and broke on cross-epoch fork
   attestations surfaced by the compliance suite.

Adds support for two compliance-only check fields:

- `viable_for_head_roots_and_weights` (consensus-specs#3831): compared
  via `getViableHeads()`. Both sides are sorted by root before
  comparison since the spec doesn't fix order.
- `head_payload_status` (gloas): mapped between our internal enum
  ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0,
  FULL=1, PENDING=2).

Pass rate against the latest comptests workflow `small.tar.gz` artifact:

  fulu/fork_choice_compliance: 253/1472 cases pass (17.2%)

Top remaining failures:
- ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced
  a `block.proposer_index == get_beacon_proposer_index(head_state)`
  guard in `update_proposer_boost_root` that we do not yet implement;
  affects all forks (not just gloas equivocation handling). Tracked
  for follow-up alongside ChainSafe#9233.
- ~1% `Invalid viable heads` — proposer-boost rounding on minimal
  preset (see `getViableHeads()` weight note).
@nflaig

nflaig commented Jul 8, 2026

Copy link
Copy Markdown
Member

@ensi321 is this still relevant?

ensi321 and others added 4 commits July 8, 2026 20:03
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hardcoding PayloadStatus.PENDING in the weak-parent lookup crashes at
the gloas fork transition: the first gloas block's parent is pre-gloas
(FULL-only), and getNodeIndexByRootAndStatus throws INVALID_NODE_INDEX
when PENDING is requested for a pre-gloas root. The adjacent-parent
guard does not short-circuit this path at the boundary. Use
parentBlock.payloadStatus instead, matching the pattern already used
for the strong-variant lookup elsewhere in this file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ensi321

ensi321 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

@ensi321 is this still relevant?

For some reason, our unstable is now passing these tests even without this PR and we failed them previously

(name.includes("gloas") &&
(name.includes("simple_attempted_reorg_without_enough_ffg_votes") ||
name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch") ||
name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_previous_epoch") ||
name.includes("include_votes_another_empty_chain_without_enough_ffg_votes_current_epoch"))),

But we still need to implement shouldApplyProposerBoost to enforce the correct behaviour.

Added some new tests ethereum/consensus-specs#5441 to make sure this is covered.

@ensi321
ensi321 marked this pull request as ready for review July 9, 2026 14:36
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.49%. Comparing base (e6d38ec) to head (d2bb968).
⚠️ Report is 3 commits behind head on unstable.

Additional details and impacted files
@@            Coverage Diff            @@
##           unstable    #9233   +/-   ##
=========================================
  Coverage     52.49%   52.49%           
=========================================
  Files           848      848           
  Lines         60487    60486    -1     
  Branches       4466     4465    -1     
=========================================
  Hits          31755    31755           
  Misses        28670    28670           
+ Partials         62       61    -1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d2bb9684f2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/fork-choice/src/forkChoice/forkChoice.ts Outdated

// Indicate whether block arrives in a timely manner ie. before the 4 second mark
// Spec: Store.block_timeliness[ATTESTATION_TIMELINESS_INDEX]
timeliness: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may want to use a single block_timeliness: number to represent these 2 flags, cc @wemeetagain to confirm

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a real benefit to this? Using bits impact readability, and we are only saving one boolean field per ProtoNode

@twoeths twoeths left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implemented is_head_weak() in #9654
need to have it landed before this PR

wemeetagain pushed a commit that referenced this pull request Jul 24, 2026
**Motivation**

- is_head_weak() is needed for should_apply_proposer_boost() #9233
- we also need it for `getProposerHead()`

**Description**
- implement cross-fork
[is_head_weak()](https://github.com/ethereum/consensus-specs/blob/6408b990c0b4e58d4f50ae49b59384e82e5e06d8/specs/gloas/fork-choice.md?plain=1#L739)
spec api and use it in `getProposerHead()`
- track attestation weight in ProtoNode separately, weight still means
total weight
- back propagate boost deltas and attestation deltas separately
- also implement is_parent_strong()


**AI Assistance Disclosure**

- created with the help of Claude

---------

Co-authored-by: Tuyen Nguyen <twoeths@users.noreply.github.com>
Co-authored-by: bing <spiralladder@fastmail.com>
ensi321 and others added 2 commits July 24, 2026 18:08
…roposer-boost

# Conflicts:
#	packages/beacon-node/test/spec/presets/fork_choice.test.ts
…ProposerBoost

is_head_weak() landed via #9654 with the exact gloas semantics (boost-excluded
attestation score + committee-scoped equivocator balance add-back), so the
weak-parent check can call it directly instead of approximating with raw
node.weight against the reorg threshold.

Also adapt findEquivocatingBlocks to the current VariantIndices layout via
getDefaultNodeIndex, and add the new ProtoBlock fields to test fixtures that
were added on unstable since the last merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensi321 and others added 2 commits July 24, 2026 21:04
should_apply_proposer_boost judges the parent via is_head_weak against the
attestations known to the store, but the decision ran before applyScoreChanges()
landed this round's deltas, so it read the previous round's attestation scores.
A parent crossing the weak threshold on the pending batch kept the boost
withheld, and a newly detected equivocator kept its discounted vote counted.

For a gloas boosted block, split the score update in two passes: attestation
deltas first, then the boost decision, then the boost deltas. Pre-gloas keeps
the single pass since the boost is unconditional there.

Also replace findEquivocatingBlocks() with hasEquivocatingBlock(): the only
caller tests emptiness, so return on first match instead of allocating a list.

The two ordering tests fail on the previous commit and pass on this one; the
spec vectors cannot catch this because the check step recomputes the head,
self-healing the transient decision before assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measures the two-pass applyScoreChanges overhead against the single-pass
pre-gloas row: 2.96 ms/op vs 2.72 ms/op at vc 600k bc 64 (~9%), paid only
while a gloas block holds proposer boost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
*
* Child class can overwrite this for testing purpose.
*/
protected isBlockPtcTimely(block: BeaconBlock, blockDelaySec: number): boolean {

@nflaig nflaig Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this should only be relevant in case of proposer equivocation to make sure the next proposer can see it and can be aware of the equivocation in time?

so using the ptc timing seems pretty arbitrary to me but the purpose if I understand this correctly is to figure out if the it was an "early equivocation"

should this be renamed?

Suggested change
protected isBlockPtcTimely(block: BeaconBlock, blockDelaySec: number): boolean {
protected isBlockEquivocationTimely(block: BeaconBlock, blockDelaySec: number): boolean {

Edit: after thinking more about it, not sure the rename is better because it could suggest that the block is an equivocation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I like isBlockPtcTimely

continue;
}
const node = this.nodes[nodeIndex];
if (node !== undefined && node.slot === slot && node.proposerIndex === proposerIndex && node.ptcTimeliness) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this can happen right now, if we receive a equivocating block over gossip, we would just ignore it

// [IGNORE] The block is the first block with valid signature received for the proposer for the slot, signed_beacon_block.message.slot.
const proposerIndex = block.proposerIndex;
if (chain.seenBlockProposers.isKnown(blockSlot, proposerIndex)) {
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.REPEAT_PROPOSAL, proposerIndex});
}

this is kinda related to #9757, need to see if we wanna use seenBlockProposers or fork choice for this, but generally, the equivocation check on the api shouldn't care about timings, if there was an equivocation when submitting the payload, then it's safe to withhold as you don't need to pay the bid.value to the proposer

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the IGNORE is more about the gossipsub spec, we should still process it and add to forkchoice #9805
some peers may still vote for the 2nd block, we'll use UnknownBlockInput sync to download, process and add to forkchoice soon anyway

Comment thread packages/beacon-node/test/spec/utils/forkChoiceTestRunner.ts Outdated
Comment on lines +593 to +595
this.protoArray.applyScoreChanges({attestationDeltas, proposerBoost: null, ...checkpoints});
const proposerBoost = this.shouldApplyProposerBoost() ? this.getProposerBoost() : null;
this.protoArray.applyScoreChanges({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is calling applyScoreChanges twice fine? cc @twoeths

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now applyScoreChanges() does 2 things:

  • (1) update node's weights
  • (2) update best child/descendants

we can improve by calling 1) twice and call 2) in the end
it's more like a refactor and does not affect fulu so we can just leave a TODO and address in another PR I guess

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added TODO comment

Comment thread packages/fork-choice/src/forkChoice/forkChoice.ts Outdated
@ensi321

ensi321 commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Going to reflect the changes from ethereum/consensus-specs#5515 also

ensi321 added a commit that referenced this pull request Aug 26, 2026
The alpha.14 comptests were regenerated, so the seed-embedding case names
changed again. Full re-run and re-triage of the gloas suite:

- #9821 (exact Gwei weights) fixed the proposer boost weight quantization
  divergence (#9694); that skip bucket is removed.
- #9864 (recompute head after pulling up checkpoints) fixed the stale
  cached head read in the proposer boost dependent-root gate (#9666);
  both remaining pins are removed.
- 127 cases remain skipped for consensus-specs#5496 (childless
  payload-status variants bypass the filter_block_tree FFG check),
  regenerated names. This is the only remaining failure bucket.

Suite result with #9233 merged locally: 1345 passed, 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Vw1BUX36pwcv52fGr3n1C
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

6 participants